// 19.6 union：一种节省空间的类
/**
 * 联合（union）是一种特殊的类。一个union可以有多个数据成员，但是在任意时刻只有一个数据成员可以有值。当我们给union的某个成员赋值之后，该union的其他成员就变成未定义的状态了。
 *   分配给一个union对象的存储空间至少要能容纳它的最大的数据成员。
 * union不能含有引用类型的成员，除此之外，它的成员可以是绝大多数类型。在C++11新标准中，含有构造函数或析构函数的类类型也可以作为union的成员类型。
 *   union可以为其成员指定public、protected和private等保护标记。默认情况下，union的成员都是公有的，这一点与struct相同。
 * union可以定义包括构造函数和析构函数在内的成员函数。但是由于union既不能继承自其他类，也不能作为基类使用，所以在union中不能含有虚函数。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include <functional>
#include "../VisualStudio2012/15/Quote.h"
#include "../VisualStudio2012/12/TextQuery.h"
#include <tuple>
#include <bitset>
#include <regex>
#include <random>
#include <cmath>
#include <iomanip>
#include <cstdio>
#include <cstdlib>
#include <typeinfo>

int main()
{
    // 定义union
    /*union提供了一种有效的途径使得我们可以方便地表示一组类型不同的互斥值。举个例子，假设我们需要处理一些不同种类的数字数据和字符数据，则在此过程中可以定义一个union来保存这些值：[插图][插图]
// Token类型的对象只有一个成员，该成员的类型可能是下列类型中的任意一种
union Token {
    // 默认情况下成员是公有的
    char cval;
    int ival;
    double dval;
};
    */

    // 使用union类型
    /*union的名字是一个类型名。和其他内置类型一样，默认情况下union是未初始化的。我们可以像显式地初始化聚合类（参见7.5.5节，第266页）一样使用一对花括号内的初始值显式地初始化一个union：[插图]
Token first_token = {'a'}; // 初始化cval成员
Token last_token;          // 未初始化的token对象
Token *pt = new Token;     // 指向一个未初始化的Token对象的指针
      我们使用通用的成员访问运算符访问一个union对象的成员：[插图]
last_token.cval = 'z';
pt->ival = 42;
      为union的一个数据成员赋值会令其他数据成员变成未定义的状态。因此，当我们使用union时，必须清楚地知道当前存储在union中的值到底是什么类型。
        如果我们使用错误的数据成员或者为错误的数据成员赋值，则程序可能崩溃或出现异常行为，具体的情况根据成员的类型而有所不同。
    */

    // 匿名union
    /*匿名union（anonymous union）是一个未命名的union，并且在右花括号和分号之间没有任何声明（参见2.6.1节，第65页）。
        一旦我们定义了一个匿名union，编译器就自动地为该union创建一个未命名的对象：[插图]
union {
    char cval;
    int ival;
    double dval;
}; // 定义一个未命名的对象，我们可以直接访问它的成员
cval = 'c'; // 未刚刚定义的未命名的匿名union对象赋一个新值
ival = 42;  // 该对象当前保存的值是42
      匿名union不能包含受保护的成员或私有成员，也不能定义成员函数。
    */

    // 含有类类型成员的union
    /*C++的早期版本规定，在union中不能含有定义了构造函数或拷贝控制成员的类类型成员。C++11新标准取消了这一限制。
        不过，如果union的成员类型定义了自己的构造函数和/或拷贝控制成员，则该union的用法要比只含有内置类型成员的union复杂得多。
      如果我们想将union的值改为类类型成员对应的值，或者将类类型成员的值改为一个其他值，则必须分别构造或析构该类类型的成员：
        当我们将union的值改为类类型成员对应的值时，必须运行该类型的构造函数；反之，当我们将类类型成员的值改为一个其他值时，必须运行该类型的析构函数。
      当union包含的是内置类型的成员时，编译器将按照成员的次序依次合成默认构造函数或拷贝控制成员。
        但是如果union含有类类型的成员，并且该类型自定义了默认构造函数或拷贝控制成员，则编译器将为union合成对应的版本并将其声明为删除的（参见13.1.6节，第450页）。
      例如，string类定义了五个拷贝控制成员以及一个默认构造函数。如果union含有string类型的成员，并且没有自定义默认构造函数或某个拷贝控制成员，则编译器将合成缺少的成员并将其声明成删除的。
        如果在某个类中含有一个union成员，而且该union含有删除的拷贝控制成员，则该类与之对应的拷贝控制操作也将是删除的。
    */
    
    // 使用类管理union成员
    /*对于union来说，要想构造或销毁类类型的成员必须执行非常复杂的操作，因此我们通常把含有类类型成员的union内嵌在另一个类当中。这个类可以管理并控制与union的类类型成员有关的状态转换。
        举个例子，我们为union添加一个string成员，并将我们的union定义成匿名union，最后将它作为Token类的一个成员。此时，Token类将可以管理union的成员。
      为了追踪union中到底存储了什么类型的值，我们通常会定义一个独立的对象，该对象称为union的判别式（discriminant）。我们可以使用判别式辨认union存储的值。
        为了保持union与其判别式同步，我们将判别式也作为Token的成员。我们的类将定义一个枚举类型（参见19.3节，第736页）的成员来追踪其union成员的状态。
      在我们的类中定义的函数包括默认构造函数、拷贝控制成员以及一组赋值运算符，这些赋值运算符可以将union的某种类型的值赋给union成员：[插图][插图]
class Token {
public:
    // 因为union含有一个string成员，所以Token必须定义拷贝控制成员
    // 定义移动构造函数和移动赋值运算符的任务留待本届练习完成
    Token(): tok(INT), ival{0} { }
    Token(const Token &t) : tok(t.tok) { copyUnion(t); }
    // 如果union含有一个string成员，则我们必须销毁它，参见19.1.2节
    ~Token() { if(tok == STR) sval.~string(); }
    // 下面的赋值运算符负责设置union的不通成员
    Token &operator=(const std::string&);
    Token &operator=(char);
    Token &operator=(int);
    Token &operator=(double);
private:
    enum { INT, CHAR, DBL STR } tok; // 判别式
    union { // 匿名union
        char cval;
        int ival;
        double dval;
        std::string sval;
    }; // 每个Token对象含有一个该未命名union类型的未命名成员
    // 检查判别式，然后酌情拷贝union成员
    void copyUnion(const Token&);
};
      因为我们的union含有一个定义了析构函数的成员，所以必须为union也定义一个析构函数以销毁string成员。和普通的类类型成员不一样，作为union组成部分的类成员无法自动销毁。
        因为析构函数不清楚union存储的值是什么类型，所以它无法确定应该销毁哪个成员。我们的析构函数检查被销毁的对象中是否存储着string值。
        如果有，则类的析构函数显式地调用string的析构函数（参见19.1.2节，第729页）释放该string使用的内存；反之，如果union存储的值是内置类型，则类的析构函数什么也不做。
    */

    // 管理判别式并销毁string
    /*类的赋值运算符将负责设置tok并为union的相应成员赋值。和析构函数一样，这些运算符在为union赋新值前必须首先销毁string：[插图]
Token &Token::operator=(int i) {
    if(tok == STR) sval.~string(); // 如果当前存储的是string，释放它
    ival = i;                      // 为成员赋值
    tok = INT;                     // 更新判别式
    return *this;
}
      double和char版本的赋值运算符与int赋值运算符非常相似，读者可以在本节的练习中尝试使用这两个运算符。string版本与其他几个有所区别，原因是string版本必须管理与string类型有关的转换：[插图][插图]
Token &Token::operator=(const std::string &s) {
    if(tok == STR) 
        sval = s; // 如果当前存储的是string，可以直接赋值
    else
        new(&sval) string(s); // 否则需要先构造一个string
    tok = STR;                // 更新判别式
    return *this;
}
    */

    // 管理需要拷贝控制的联合成员
    /*
void Token::copyUnion(const Token &t) {
    switch(t.tok) {
        case Token::INT: ival = t.ival; break;
        case Token::CHAR: cval = t.cval; break;
        case Token::DBL: dval = t.dval; break;
        // 要想拷贝一个string可以使用定位new表达式来构造它，参见19.1.2节
        csae Token::STR: new(&sval) string(t.sval); break;
    }
}
Token &token::operator=(const std::string &s) {
    // 如果此对象的值是string而t的值不是，则我们必须释放原来的string
    if(tok == STR && t.tok != STR) sval.~string();
    if(tok == STR && t.tok == STR)
        sval = t.sval; // 无需构造一个新的string
    else
        copyUnion(t);  // 如果t.tok是STR，则需要构造一个新的string
    tok = t.tok;
    return *this;
}
    */

    return 0;
}